import ddf.minim.*;

Minim minim;
AudioPlayer song;

Title title;
Credits credits;
PictureWave bridge;
Disolve mrt;
Disolve hello;
MetaPicture welho;
Threshold otto;
Snakes linkki;
Snakes dragon;
Greets greets;
boolean first = true;

void setup() 
{
  size(1024,768);
  frameRate(30);
  randomSeed(2);
  minim = new Minim(this);
  song = minim.loadFile("demo.wav");
  song.play();
  title = new Title();
  credits = new Credits(); 
  bridge = new PictureWave("silta.png");
  mrt = new Disolve("mrt.png",false, true);
  hello = new Disolve("hello.png",false, true);
  welho = new MetaPicture();
  otto = new Threshold();
  linkki = new Snakes(true, "linkki.png",4);
  dragon = new Snakes(false, "dragon.png",4);
  greets = new Greets();
  background(0);
}



void draw() 
{ 
  real();
  
  

}
void real() {
  if (millis() < 30000) {
    title.draw();
  }
  else if (millis() < 37000) {
    title = null;
    credits.fade();
  }
  else if (millis() < 72200) {
    credits.draw();
  }
  else if (millis() < 89200) {
    credits = null;
    bridge.draw();
  }
  else if ( millis() < 106700) {
    bridge = null;
    linkki.draw();
  }
  else if ( millis() < 124000) {
    linkki = null;
    mrt.draw();
  }
  else if( millis() < 142000) {
    mrt = null;
    hello.draw();
  }
  else if ( millis() < 159500) {
    hello = null;
    if (first){
      first = false;
      background(0);
    }
    dragon.draw();
  }
  else if ( millis() < 179000) {
    dragon = null;
    otto.draw();
  }
  else if (millis() < 214000) {
    otto = null;
    welho.draw();
  }
  else if (millis() < 270000){
     greets.draw();
  }
  else{
    background(0);
  }
}



class Dummy {
  Dummy()
  {
  }
  void draw()
  {
    update();
  }
  void update() {
  }
}

class Greets {
  Disolve boobs;
  ArrayList names;
  int name_i = 1;
  String name;
  
  
  float start_y = 600;
  float end_y =  300;
  float fade_y = 400;
  float current = start_y;
  float speed = 4;
  float a = 0;
  float da = 7;
  boolean fading = false;
  PFont f;
  
  Greets()
  {
    this.boobs = new Disolve("tits2.png",false, false);
    f = createFont("Arial bold", 85);

    this.names = new ArrayList();
    this.names.add("King Thrill");
    this.names.add("Tekotuotanto");
    this.names.add("Hot Lambda Lovin'");
    this.names.add("Codegrove");
    this.names.add("Ziticca");
    this.names.add("Hekku2");
    this.names.add("Zouppen");
    this.names.add("Eladith");
    this.names.add("jaakkop");
    
    this.name = (String) this.names.get(0);
  }
  
  void draw()
  {
    textFont(f);
    this.boobs.draw();
    fill(137,0,188);
    text("We Greet",20, 120);
    text("with tits:",20,200);
    
    if (this.current <= this.fade_y && !this.fading){
      this.da *= -1;
      this.fading = true;
    }
    
    
    if (this.current <= this.end_y){
      this.current = start_y;
      this.a = 0;
      this.da *= -1;
      this.fading = false;
      
      if (this.name_i < this.names.size()){
        this.name = (String) this.names.get(this.name_i);
        this.name_i++;
      }
      else{
        this.name = " ";
      }

    }
    if (this.a < 200 || this.fading){
      this.a += this.da;
    }
    fill(47,96,255,this.a);
    text(this.name, 20, this.current);
    this.current -= this.speed;
    

  }
}


class Snake_pixel {
  float x, y;
  float dx, dy;
  color c;
  
  Snake_pixel(float x, float y, float dx, float dy, color c)
  {
    this.x = x;
    this.y = y;
    this.dx = dx;
    this.dy = dy;
    this.c = c;
  }
  
  void draw()
  {
    stroke(c);
    point(x,y);
  }
  void update() {
  }
}



class Snakes {
  ArrayList points;
  PImage pic;
  boolean clear;
  
  Snakes(boolean clear, String picture, int p)
  {
    this.clear = clear;
    this.pic = loadImage(picture);
    this.pic.loadPixels();
    this.points = new ArrayList();
    color c;
    for(int y=p/2; y<height; y+=p){
      for(int x=p/2; x<width; x+=p){
        c = this.pic.pixels[y*width+x];
        this.points.add( new Snake_pixel(x,y,random(-10,10),random(-10,10), c) );
      }
    }
    
  }
  void draw()
  {
    float nx, ny;
    if (this.clear){
      background(0);
    }
    for(int i =0; i< this.points.size(); i++){
      Snake_pixel point = (Snake_pixel) this.points.get(i);
      nx = point.x + point.dx*0.014;
      ny = point.y + point.dy*0.014;
      
      if (nx <= 0 || nx >= width){
        point.dx *= -1;
      }
      if (ny <= 0 || ny >= height ){
        point.dy *= -1;
      }
      
      try{
        if (this.pic.pixels[int(ny*width+nx)] != point.c){
          point.x = nx;
          point.y = ny; 
          point.draw();
        }
      }
      catch (Exception e){
        //tyhjää täynnä
      }
        point.dx = random(-10,10);
        point.dy = random(-10,10);
    }
  }

}



class Threshold {
  PImage pic;
  float thres, speed;
  PGraphics pg;
  PGraphics plasma;
  float blue,red,green;
  float bb, br, bg;
  float bmin, bmax;

  Threshold()
  {
    this.pic = loadImage("otto.png");
    this.pg = createGraphics(this.pic.width, this.pic.height, P2D);
    this.plasma = createGraphics(this.pic.width, this.pic.height, P2D);
    this.thres = 0.5;
    this.speed = 0.005;
    
    this.blue = 5;
    this.red = 2;
    this.green = 1;
    this.bb = this.br = this.bg = 150;
    this.bmin = 50;
    this.bmax = 205;
    
  }
  void draw()
  {
    update();
    background(0);

    float xc = 25;
    int pixelSize = 2;
    int timeDisplacement = frameCount;
    float calculation1 = sin( radians(timeDisplacement * 0.61655617));
    float calculation2 = sin( radians(timeDisplacement * -3.6352262));
    plasma.beginDraw();
    plasma.loadPixels();
    for (int x = 0; x < pg.width; x++, xc += pixelSize)
    {
      float  yc    = 25;
      float s1 = 128 + 128 * sin(radians(xc) * calculation1 );

      for (int y = 0; y < plasma.height; y++, yc += pixelSize)
      {
        float s2 = 128 + 128 * sin(radians(yc) * calculation2 );
        float s3 = 128 + 128 * sin(radians((xc + yc + timeDisplacement * 5) / 2));  
        float s  = (s1+ s2 + s3) / 3;
        plasma.pixels[x+y*plasma.width] = color(s, 255 - s / 2.0, 255);
      }
    }   
    plasma.updatePixels();
    plasma.endDraw();


    image(plasma,0,0,width,height); 



    color c;
    color w = color(255);
    color cool = color(this.br,this.bg,this.bb);
    color a = color(255,255,0,0);
    this.pg.beginDraw();
    this.pg.loadPixels();
    float r, g, b;
    float sum;

    for (int y=0; y < this.pic.height; y++) {
      for (int x=0; x < this.pic.width; x++) {
        c = this.pic.pixels[y*this.pic.width+x];
        r = red(c);
        g = green(c);
        b = blue(c);

        sum = (r+g+b) / 765.0;

        if ( sum <= this.thres) {
          c = cool;
        }
        else {
          c = a;
        }

        this.pg.pixels[y*this.pg.width+x] = c;
      }
    }
    this.pg.updatePixels();
    this.pg.endDraw();

    image(this.pg,0,0,width,height);
  }

  void update() {
    if ( this.thres <= 0.2 || this.thres >= 0.8) {
      this.speed *= -1;
    }
    this.thres += this.speed;
    
      if (this.bb > this.bmax || this.bb < this.bmin) { 
        this.blue *= -1;
      }
      this.bb += this.blue;
      if (this.bg > this.bmax || this.bg < this.bmin) { 
        this.green *= -1;
      }
      this.bg += this.green;
      if (this.br > this.bmax || this.br < this.bmin) { 
        this.red *= -1;
      }
      this.br += this.red;
    
    
  }
}


class Metaball {
  float x, y;
  float dx, dy;
  float speed;
  float radius;

  Metaball(float x, float y, float dx, float dy, float speed, float radius) {
    this.x = x;
    this.y = y;
    this.dx = dx;
    this.dy = dy;
    this.speed =speed;
    this.radius = radius;
  }

  float distance(float x, float y) {
    return (this.radius / sqrt( (x - this.x) * (x - this.x) + ( y - this.y ) * ( y - this.y ) ) );
  }

  void update()
  {
    if( this.x <= 0 || this.x >= 512) {
      this.dx *= -1;
    }
    if ( this.y <= 0 || this.y >= 384) {
      this.dy *= -1;
    }

    this.x += this.dx*speed; 
    this.y += this.dy*speed;
  }
}


class MetaPicture {
  PImage pic;
  ArrayList balls;
  PGraphics pg;

  MetaPicture()
  {
    this.pic = loadImage("wleho.png");
    this.pic.loadPixels();
    this.pg = createGraphics(this.pic.width, this.pic.height, P2D);
    this.balls = new ArrayList();
    balls.add( new Metaball(100,100,random(-10,10),random(-10,10),0.000005,40) );
    balls.add( new Metaball(30,30,random(-10,10),random(-10,10),0.000005,40) );
    balls.add( new Metaball(300,300,random(-10,10),random(-10,10),0.000005,40) );
  }

  void draw()
  {
    background(0);
    float sum;
    color c;

    this.pg.beginDraw();
    this.pg.loadPixels();

    for (int y=0; y < this.pic.height; y++) {
      for (int x=0; x < this.pic.width; x++) {
        sum = 0;
        for (int i=0; i <balls.size(); i++) {
          Metaball ball = (Metaball) this.balls.get(i);
          ball.update();
          sum += ball.distance(float(x), float(y));
        }
        c = this.pic.pixels[y*this.pic.width+x];

        if (sum > 0.9) {
          float b = blue(c) + sum * 40 - 15;
          float g = green(c) + sum* 40 - 15;
          float r = red(c) + sum* 40 -15;
          c = color(r,g,b);
        }
        else if( sum > 0.7) {
          float b = blue(c) + sum * 20 -50;
          float g = green(c) + sum* 20 -50;
          float r = red(c) + sum* 20 - 50;
          c = color(r,g,b);
        }
        else if( sum > 0.68) {
          float b = blue(c) ;
          float g = green(c) ;
          float r = red(c) ;
          c = color(r,g,b);
        }
        else {
          float b = blue(c) - sum * 300 -20;
          float g = green(c) - sum* 300 -20;
          float r = red(c) - sum* 300 -20;
          c = color(r,g,b);
        }

        this.pg.pixels[int(y*this.pg.width+x)] = c;
      }
    }
    this.pg.updatePixels();
    this.pg.endDraw();

    image(this.pg,0,0,width,height);
  }
}

class Pixel {
  float x,y;
  float dx, dy;
  float speed;
  color c;
  float rate = 0.0001;

  Pixel(float x, float y, float dx, float dy, float speed, color c)
  {
    this.x = x;
    this.y = y;
    this.dx = dx;
    this.dy = dy;
    this.speed = speed;
    this.c = c;
  }

  void draw()
  {
    
    ellipse(this.x, this.y, 8, 8);
    fill(c);
  }

  void update(boolean fade, boolean move) {
    if (move){
      this.x += this.dx * this.speed;
      this.y += this.dy * this.speed;
    }
    
    if (fade){
    float g = green(this.c);
    float r = red(this.c);
    float b = blue(this.c);
    float rnd = random(0,10) * this.rate;
    g -= rnd; 
    r -= rnd; 
    b -= rnd;

    this.c = color(r,g,b);
    }
  }
}



class Disolve {
  PImage pic;
  ArrayList dots;
  boolean fade;
  boolean move;

  Disolve(String picture, boolean fade, boolean move)
  {
    this.fade = fade;
    this.move = move;
    this.pic = loadImage(picture);
    this.pic.loadPixels();
    dots = new ArrayList();
    color c;

    for(int y=4; y < height; y+=8)
    {
      for(int x=4; x<width; x+=8)
      {
        int index = y*1024 + x;
        c = this.pic.pixels[index];
        this.dots.add( new Pixel(x,y, random(-10,10), random(-10,10),0.005,c) );
      }
    }
  }
  void draw()
  {
    background(0);
    //image(this.pic,0,0);
    noStroke();


    for(int i=0; i < this.dots.size(); i++)
    {
      Pixel dot = (Pixel) this.dots.get(i);
      dot.update(this.fade,this.move);
      dot.draw();
    }
  }

}


class PictureWave {
  PImage pic;
  PGraphics pg;
  float deg = 360;
  float rad = radians(deg);
  float offset = 360;
  color black = color(0);

  PictureWave(String picture)
  {
    this.pic = loadImage(picture);
    this.pic.loadPixels();
    pg = createGraphics(this.pic.width,this.pic.height+10, P2D);
  }
  void draw()
  {
    background(0);
    color c; 
    int index;
    this.pg.beginDraw();
    this.pg.loadPixels();
    for (int y=0; y<this.pic.height;y++)
    {
      for (int x=0; x<this.pic.width;x++) {

        index = y*this.pic.width + x;
        c = this.pic.pixels[index];
        float dx = x + sin(this.rad) * 10;

        if (dx >= 0) {
          this.pg.pixels[int(y*this.pg.width)+int(dx)] = c;
        }
        update();
      }
      this.deg = this.offset;
      this.rad = radians(this.deg);
    }
    this.offset += 5;
    this.pg.updatePixels();
    this.pg.endDraw();

    image(this.pg,-30,-20,width+30,height+50);
  }

  void update() {
    this.deg++;
    this.rad = radians(this.deg);
  }
}


class Title {
  String[] n = new String[3];
  String time;
  ArrayList texts;
  String message;
  PFont timeF;
  PFont textsF;
  float speed = 5.6;
  float position = 1024;

  Title()
  {
    timeF = createFont("Arial", 120);
    textsF = createFont("Arial", 40);
    this.texts = new ArrayList();
    this.texts.add("Precalc");
    this.texts.add("Fetching source");
    this.texts.add("Compiling source");
    this.texts.add("Calculating variables");
    this.texts.add("Initializing matrices");
    this.texts.add("Initializing pixels");
    this.texts.add("Processing thresholds");
    this.texts.add("Ordering pepperoni & blue cheese pizza");
    this.texts.add("and some beer");
    this.texts.add("Starting the demo");
    
    this.message = "";
    for (int i=0; i<this.texts.size(); i++){
      this.message = this.message + (String) this.texts.get(i) + "          ";
    }
  }
  void draw()
  {
    background(0);
    update();
    textFont(this.timeF);
    text(time,290,height/2 - 100);
    text("realtime graphics",50, height/2 +50);
    textFont(this.textsF);
    text(this.message, this.position,600);
    this.position -= this.speed;
  }
  void update() {
    if ( hour() < 10) {
      this.n[0] =  "0"+Integer.toString(hour());
    }
    else {
      this.n[0] =  Integer.toString(hour());
    }
    if ( minute() < 10) {
      this.n[1]= "0"+Integer.toString(minute());
    }
    else {
      this.n[1]= Integer.toString(minute());
    }
    if (second() < 10) {
      this.n[2] = "0"+Integer.toString(second());
    }
    else {
      this.n[2] = Integer.toString(second());
    }
    this.time = "";
    this.time = join(this.n, ":");
  
    
  }
}

class Dot {
  int x, y;
  int size;
  float alpha;
  boolean important;
  float fade_a = 10;

  Dot(int x, int y, int size, float alpha, boolean important) {
    this.x = x;
    this.y = y;
    this.size = size;
    this.alpha = alpha;
    this.important = important;
  }

  void draw()
  {
    if(this.alpha != 0) {
      ellipse(this.x, this.y, this.size, this.size);
      fill(this.alpha);
    }
  }

  void fade() {
    ellipse(this.x, this.y, this.size, this.size);
    fill(this.fade_a);
    this.fade_a += random(0,10) *0.1;
  }

  boolean update()
  {
    if (this.alpha > 0) {
      if (this.important)
      {
        this.alpha -= 1.5;
      }
      else
      {
        this.alpha -=5;
      }
      return false;
    }
    else
    {
      return true;
    }
  }
}

class Credits {
  ArrayList all;
  ArrayList current;
  int current_i = 0;

  Credits()
  {
    PImage jypa = loadImage("jypa.png");
    PImage indei = loadImage("indei.png");
    PImage pakko = loadImage("pakko.png");
    PImage presents = loadImage("presents.png");
    PImage secondDimension = loadImage("second.png");
    PImage instanssi = loadImage("instanssi.png");
    instanssi.loadPixels();
    jypa.loadPixels();
    indei.loadPixels();
    pakko.loadPixels();
    presents.loadPixels();
    secondDimension.loadPixels();

    this.all = new ArrayList();

    pixelate(this.all, instanssi);
    pixelate(this.all, pakko);
    pixelate(this.all, presents);
    pixelate(this.all, secondDimension);
    pixelate(this.all, jypa);
    pixelate(this.all, indei);

    current = (ArrayList) this.all.get(0);
  }

  void pixelate(ArrayList a, PImage pic) {
    ArrayList dump = new ArrayList();
    color c;
    color black = color(0);
    for(int y=4; y < height; y+=8)
    {
      for(int x=4; x<width; x+=8)
      {
        int index = y*1024 + x;
        c = pic.pixels[index];
        if( c != black)
        {
          dump.add(new Dot(x,y,8,255,true));
        }
        else
        {
          dump.add(new Dot(x,y,8,240,false));
        }
      }
    }
    a.add(dump);
  }

  void draw()
  {
    update();
    background(0);
    noStroke();
    float n;
    boolean dead = false;
    for(int i=0; i < this.current.size(); i++)
    {
      Dot dot = (Dot) current.get(i);
      n = random(1,10);
      if (n > 6) {
        dead = dot.update();
      }
      if (dead) {
        current.remove(i);
      }
      else {
        dot.draw();
      }
    }
  }

  void fade()
  {
    background(0);
    noStroke();
    for(int i=0; i < this.current.size(); i++)
    {
      Dot dot = (Dot) current.get(i);
      dot.fade();
    }
  }

  void update() 
  {
    if (this.current.size() < 6000 && this.current_i < this.all.size()) {
      this.current = (ArrayList) this.all.get(this.current_i++);
    }
  }
}

